#include<iostream>
#include<string>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

using namespace std;

const int basesize=1024;
const int argvnum=64;
const int envnum=64;

//定义全局的命令行参数
char* gargv[argvnum];
int gargc=0;

// 全局变量
int lastcode=0;

//shell工作路径
char pwd[basesize];
char pwdenv[basesize];

// 我的系统的环境变量
char *genv[envnum];

string GetUserName()
{
    string name = getenv("USER");
    return name.empty()? "None" : name;
}

string GetHostName()
{
    string hostname = getenv("HOSTNAME");
    return hostname.empty()? "None" : hostname;
}

string GetPwd()
{
    //string pwd = getenv("PWD");
    
    if(getcwd(pwd,sizeof(pwd))==nullptr) return "None";
    snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);
    putenv(pwdenv);
    
    return pwd;

    //return pwd.empty()? "None" : pwd;
}

string LastDir()
{
    string curr=GetPwd();;
    if(curr == "/" || curr == "None") return curr;
    else 
    {
        size_t pos=curr.rfind('/');
        return curr.substr(pos+1);
    }
}

string  MakeCommandLine()
{
    //[root@VM-8-2-centos myshell]#
    char command_line[basesize];
    snprintf(command_line,basesize,"[%s@%s %s]# ",GetUserName().c_str(),GetHostName().c_str(),LastDir().c_str());
    return command_line;
}


void PrintCommandLine() //1、打印命令行提示符
{
    printf("%s",MakeCommandLine().c_str());
    fflush(stdout);
}


bool GetCommandLine(char command_buffer[],int size) //2、获取用户命令
{
    //我们将用户输入的命令行你，当作一个完整的字符串
    //"ls -a -l -n"
    char* result = fgets(command_buffer,size,stdin);
    if(!result)
    {
        return false;
    }
    command_buffer[strlen(command_buffer)-1] = 0; //修正结尾读取的\n
    if(strlen(command_buffer)==0) return false;
    return true;
}


bool ParseCommandLine(char command_buffer[],int len) //3、分析命令
{
    (void)len;
    memset(gargv,0,sizeof gargv);
    gargc=0;
    const char* sep = " "; //设置分割符
    gargv[gargc++]=strtok(command_buffer,sep);
    
    while(gargv[gargc++]=strtok(nullptr,sep));
    gargc--;
    //strtok(nullptr, sep) 的作用是：“继续处理上一次没分割完的字符串，用同样的分隔符找下一个子串”。这种设计让 strtok 可以分多次逐步分割同一个字符串，而不需要每次都传入完整字符串。
}

//导入环境变量
void AddEnv(const char* item)
{
    int index=0;
    while(genv[index])
    {
        index++;
    }
    //genv[index++]=item; gargv[1]指向的是command_buffer中的一部分内容,后续再输入会覆盖
    genv[index]=(char*)malloc(strlen(item)+1);
    strncpy(genv[index],item,strlen(item)+1);
    genv[++index]=nullptr;

}


//shell自己执行命令,本质是shell调用自己的函数
bool CheckAndExecBuiltCommand()
{
    if(strcmp(gargv[0],"cd")==0)
    {
        //内建命令
        if(gargc == 2)
        {
            chdir(gargv[1]);   //gargv[1]就是..
            lastcode=0;
        }
        else
        {
            lastcode=1;
        }
        return true;
    }
    else if(strcmp(gargv[0],"export")==0)
    {
        //export也是内建命令
        if(gargc==2)
        {
            AddEnv(gargv[1]);
            lastcode=0;
        }
        else
        {
            lastcode=2;
        }
        return true;
    }
    else if(strcmp(gargv[0],"env")==0)
    {
        for(int i=0;genv[i];i++)
        {
            printf("%s\n",genv[i]);
        }
        lastcode=0;
        return true;
    }
    else if(strcmp(gargv[0],"echo")==0)
    {
        if(gargc==2)
        {   
            // echo $?
            // echo hello
            // echo $PATH
            if(gargv[1][0] == '$')
            {
                if(gargv[1][1]=='?')
                {
                    printf("%d\n",lastcode);
                    lastcode=0; //查了则清零
                }
                else 
                {
                    //简单写，其他情况就不做处理了
                }
            } 
            else
            {
                printf("%s\n",gargv[1]);
                lastcode=0;
            }
        }
        else 
        {
            lastcode=3;
        }
        return true;
    }
    return false;
}
void debug()
{
    printf("argc: %d\n",gargc);
    for(int i=0;gargv[i];i++)
    {
        printf("argv[%d]:%s\n",i,gargv[i]);
    }
}

//在shell中：
//有些命令，必须由子进程执行
//而有些命令，不能由于子进程执行，要由shell自己执行 --- 内建命令 built command
bool ExecuteCommand()  //4、执行命令
{
    pid_t id = fork();
    if(id<0) return false;
    else if(id==0) 
    {
        //子进程
        //1 执行命令
        // exec* 程序替换


        //execvp(gargv[0],gargv);  //错误写法，会导致testenv无法正确获取myshell的环境变量
        /*
        实现了AddEnv函数来添加环境变量，但问题的关键不在于如何添加环境变量，而在于如何将这些环境变量传递给子进程。
        AddEnv函数正确地将环境变量存入了genv数组，但在执行子进程时，
        由于使用了错误的exec函数（execvp不支持传递自定义环境变量数组），导致genv中的环境变量无法被子进程感知到。

        具体来说：
        AddEnv的作用是：将新的环境变量添加到genv数组中（这部分是正确的）。
        但子进程启动时，需要明确告诉它使用genv作为环境变量来源。而execvp函数不支持传递自定义环境变量数组，
        它只会使用进程默认的environ数组（全局环境变量）。

        所以，即使AddEnv正确维护了genv，但由于execvp不传递genv，子进程自然无法获取到这些变量。
        */
        execvpe(gargv[0],gargv,genv);
        perror("execvpe failed"); // 若execvpe失败，打印错误原因
        
        //2 退出
        exit(1);
    }
    else 
    {
        //父进程
        int status=0;
        pid_t rid = waitpid(id,&status,0);
        if(rid>0) 
        {
            if(WIFEXITED(status))
            {
                lastcode=WEXITSTATUS(status);
            }
            else 
            {
                lastcode=100;
            }
            return true;
        }
    }
    return false;
}


//作为一个shell，获取环境变量应该从系统的配置来
//我今天就直接从父shell中获取环境变量
void InitEnv()
{
    extern char** environ;
    int index=0;
    while(environ[index])
    {
        genv[index]=(char*)malloc(strlen(environ[index])+1);
        strncpy(genv[index],environ[index],strlen(environ[index])+1);
        index++;
    }
    genv[index]=nullptr;
}

int main()
{
    InitEnv();
    char command_buffer[basesize];
    while(true)
    {
        PrintCommandLine(); //1、打印命令行提示符

        //command_buffer -> output
        if(!GetCommandLine(command_buffer,basesize)) //2、获取用户命令
        {
            continue;
        }
        
        //printf("%s\n",command_buffer); //测试

            
        ParseCommandLine(command_buffer,strlen(command_buffer)); //3、分析命令
        //目标:将字符串打散 "ls" "-a" "-b" "-c" ""-d"
        //debug();
    
        if(CheckAndExecBuiltCommand()) continue;

        ExecuteCommand(); //4、执行命令
    }
    
  return 0;
}
